#!/usr/bin/php 
<?php
/*
	Project:	memcache_tools kit
	Version:	0.1 2009/07/29
	Author:		guoqing
	Copyright:
		Software License Agreement (BSD License)
	http://code.google.com/p/memcache-tools
*/

$opt = getopt("g:H:h:p:c:m:l:s:r:i:o:S:d:D:M:"); #a:

$opt_mysql_latency = array_key_exists('m', $opt) ? $opt['m'] : 0; #访问mysql的延迟$micro_seconds
$host = array_key_exists('h', $opt) ? $opt['h'] : '';  #memcached host, -h hostname:port:weight
$fromhost = array_key_exists('H', $opt) ? $opt['H'] : '';  #get memcached host from file,format same as -h 
$persistent_tcp = array_key_exists('p', $opt) ? TRUE : TRUE; #persistent tcp. 1 true, 0 false
$opt_stats = array_key_exists('S', $opt) ? $opt['S'] : -1; #only check stats, output stats interval 5s, -S sar5 sar10 可以附加定义sar时间
$loop = array_key_exists('l', $opt) ? $opt['l'] : 100;  #loop #
$parallel = array_key_exists('c', $opt) ? $opt['c'] : 1; #parallel how many process to fork
$debug = array_key_exists('d', $opt) ? $opt['d'] : 0;  #debug level
$opt_rate = array_key_exists('r', $opt) ? $opt['r'] : 1;	 #read/write rate, 0 allread, -1 allwrite
$opt_outfile = array_key_exists('o', $opt) ? $opt['o'] : ''; #write key and value to outfile
$opt_infile = array_key_exists('i', $opt) ? $opt['i'] : ''; #read key and value from infile, don't use rand key and value
$opt_itemsize_adaptor = array_key_exists('a', $opt) ? $opt['a'] : 1; #item(key,value) size scope. size=100 80-120=100-(1-0.8)*100 - 100+(1-0.8)*100
$opt_stats_dump = array_key_exists('D', $opt) ? $opt['D'] : 100;  #use stats cachedump to dump top 100
$opt_migrate = array_key_exists('M', $opt) ? $opt['M'] : '';  #format like 'h', dump data from h and save to M,report how many item sucessfully to M, how many failed(expired?).
$opt_memcached_expire = array_key_exists('e', $opt) ? $opt['e'] : 86400;
$opt_get = array_key_exists('g', $opt) ? $opt['g'] : '';
$opt_set = array_key_exists('s', $opt) ? $opt['s'] : '';

if(count($opt) == 0 or (!$host and !$fromhost)) {
	printf("
Name: 
	zgq_memcache_tools.php

SYNOPSIS:
	zgq_memcache_tools.php [OPTION]

DESCRIPTION:
	zgq_memcache_tools.php is memcache tools, include stress,stats,sar and migrate feature

	-h memcacheserver:port:weight -h memcacheserver:port:weight ...
	-H get -h list from file
			>head -3 memcache 
		c1.xx.zgq.com
		c2.xxx.zgq.com
		c3.xxx.zgq.com

		php -d open_basedir=./ zgq_memcache_tools.php -H memcache -S 300 > xxx.memcache.sar 2>err.log &
		
	-p 1 or 0 
		use persistent tcp or not
		default is 1
	-c process_number
		fork process_number to run
		default is 1
	-l loop_count
		how many times to loop
		default is 100
	-m mysql_latency(micro_seconds)
		when get miss at memcached, then take a usleep act as get data from db
	-g keyname
		get value 
	-s keyname:value:expire
		set value (debug, key and value cound not consist of Colon and other sepcial char)
	-S N second
		print stats info interval N second.
		N = 0 just print once
		other
                   :SAR:      HEAD:   c_c   con   get   set  hit%%  mis  read    writ  maxb  used%%  c_it   t_i  evic    t/u/s\n;
                   :SAR:      HEAD:   ---   ---   ---   ---  ----   ---  ----    ----  ----  ----  ----   ---  ----   ------
2009-07-29 23:16:11:SAR: localh~11:    68     0     0     0  95%%     0    20   323   512M  89%%   346K     0     0	0/0/0
2009-07-29 23:16:12:SAR: localh~11:   150   216  1011    87  92%%    84   114K     1M  1024M  89%%   693K    87     1	0/0/0
		c_c: curr_connections			read: bytes_read, Total number of bytes read by this server from network
		con: total_connections		writ: bytes_written, Total number of bytes sent by this server to network
		get: cmd_get			maxb: limit_maxbytes, total memoroy
		set: cmd_set			used: bytes/limit_maxbytes
		hit%%: get_hits/cmd_get		c_it: curr_items average
		mis: get_misses			t_i: total_items delta
		evic: evictions Number of valid items removed from cache to free memory for new items  
	    	t/u/s: everage of threads,rusage_user,rusage_system
	-d debug_level
		0 result
		1 record error
		2 record warning
		3 key info
		4
		5 value info
		default is 0
	-r read/write ratio 
		0 all read
		-1 all read

	-i input filename
		if with -c N , then will split infile(with split tool) to N files and each one process dealwith one subfile
		file format:
valuelength expiretime key
	-o output filename
		if with -c N , then will split outfile to N files and each one process dealwith one subfile
		file format:
valuelength expiretime key

	-D dump
		how many item to dump from every one slab
		N: N items to dump
		default: is 100 items
		all: try to dump all items, but due to memcached theory, only dump a little items.
	-e expiretime
		default is 1day(86400)
	-M memcacheserver:port:weight -h memcacheserver:port:weight ...
		fetch data from -h and save to -M
		filter: filter a key_list as -h memcached server
		(when set key/value in -M memserver, will use -e expiretime as expire, you should set it as small as possible)
		(only set key to -M memserver, when get key from -M failed)
		
example:
	save 1M items(created by md5(rand())) into memcached and save record to lista-? file
	php -d open_basedir=./ zgq_memcache_tools.php -h localhost:11211 -c 10 -l 100000 -o lista

 	figure out key list(filterin) when it in -h localhost and save it to file filterout
  	php -d open_basedir=./ zgq_memcache_tools.php -h localhost:11211 -M filter -i filterin -o filterout

	fetch avail value from -h and set to -M follow all key from -i file
    php zgq_memcache_tools.php -h localhost:11211 -M localhost:11212 -i cache.save-0
    php zgq_memcache_tools.php -h localhost:11211 -M localhost:11212 -i cache.save-1
    php zgq_memcache_tools.php -h localhost:11211 -M localhost:11212 -i cache.save-2

	get stats once
	php zgq_memcache_tools.php -h localhost:11212 -S 0
	
	get stats like sar per 5 seconds
	php zgq_memcache_tools.php -h localhost:11212 -S 5
");

	exit(1);
}

###--------------- global variables and initial environment:
$hosts = array();   #memcache cluster
$hosts_size = 0;
$stats = array();	#statistic

#for -M migratememcache stats
$hosts_mig = array();
$hosts_mig_size = 0;
$stats_migrate = array();

$items = array();
$ids = array();
$host_ports = array();

$keys = array();
$total_record = $parallel*$loop;  #the number of total record

$key_length = 32;	#md5(rand()) = f90c8ee486f62c42b8a5016ec94f91ad
$value_length = $key_length + 16; #$k ."-". gettimeofday(true) = f90c8ee486f62c42b8a5016ec94f91ad-1248752708.7938
$memory_utilize = 0.15;
$memory_size = round(($key_length+$value_length)*$loop/$memory_utilize);

ini_set("memory_limit", -1);
ini_set("open_basedir", "./");
#ini_set("memory_limit", $memory_size);

$op_stat = array();   #['set']['succ']++;
$op_stat['set']['succ'] = 0;
$op_stat['get']['succ'] = 0;
$op_stat['set']['fail'] = 0;
$op_stat['get']['fail'] = 0;


$infiles = array();
$infilename = '';
if($opt_infile) {
	#split file to $parallel
	$infilename = $opt_infile;
	$wc = `wc -l $infilename|cut -d" " -f1`;
	$wc = round($wc / $parallel, 0);
	$t1 = $infilename ."-";
	`split -l $wc $infilename $t1`;
	$t1 = chop(`ls $t1*`); 
	$infiles = split("\n", $t1);
	for($i=0;$i<$parallel;$i++) $infiles[$i]=chop($infiles[$i]);
}


$outfiles = array();
$outfilename = '';
if($opt_outfile) {
	$outfilename = $opt_outfile; 
	for($i=0;$i<$parallel;$i++) array_push($outfiles, $outfilename."-".$i);
}

if($debug>=3) echo "infilename=$infilename\noutfilename=$outfilename\nwc = $wc\ninfilename= $infilename\nsplit cmd=split -l $wc $infilename $tmpfilename\n";

if(is_array($host)) {
	$$hosts_size=0;
	foreach ($host as $h1) {
		@list($h, $p, $w) = split(":", $h1);
		$hosts[$hosts_size]['h']= $h ? $h : 'localhost';
		$hosts[$hosts_size]['p']= $p ? $p : 11211;
		$hosts[$hosts_size]['w'] = $w ? $w : 1;
		$hosts_size++;
	}
} elseif($host) {
	@list($h, $p, $w) = split(":", $host);
	$hosts_size=0;
	$hosts[$hosts_size]['h'] = $h ? $h : 'localhost';
	$hosts[$hosts_size]['p'] = $p ? $p : 11211;
	$hosts[$hosts_size]['w'] = $w ? $w : 1;
	$hosts_size++;
}

# -H opt
if($fromhost) {
	$froms =  split("\n", file_get_contents($fromhost));
	foreach ($froms as $from) {
		$from = chop($from);
		if(!$from) continue;
		@list($h, $p, $w) = split(":", $from);
		if(!$h) { 
			printf("ERROR: -H fromfile include wrong homename in line:%s\n", $from);
			continue;
		}
		$hosts[$hosts_size]['h'] = $h;
		$hosts[$hosts_size]['p'] = $p ? $p : 11211;
		$hosts[$hosts_size]['w'] = $w ? $w : 1;
		$hosts_size++;
	}
}

if(is_array($opt_migrate)) {
	$$opt_migrate_size=0;
	foreach ($opt_migrate as $h1) {
		@list($h, $p, $w) = split(":", $h1);
		$hosts_mig[$hosts_mig_size]['h']= $h ? $h : 'localhost';
		$hosts_mig[$hosts_mig_size]['p']= $p ? $p : 11211;
		$hosts_mig[$hosts_mig_size]['w'] = $w ? $w : 1;
		$hosts_mig_size++;
	}
} else {
	@list($h, $p, $w) = split(":", $opt_migrate);
	$hosts_mig_size=0;
	$hosts_mig[$hosts_mig_size]['h']= $h ? $h : 'localhost';
	$hosts_mig[$hosts_mig_size]['p']= $p ? $p : 11211;
	$hosts_mig[$hosts_mig_size]['w'] = $w ? $w : 1;
	$hosts_mig_size++;
}


###------------------------------------------------- function
function get_Stats($id, $mictime)
{
	global $hosts;
	global $hosts_size;
	global $stats;

	global $hosts_mig;
	global $hosts_mig_size;
	global $stats_mig;
		
	global $opt_migrate;
	
	$stats[$id]['time'] = $mictime;
	$memcache = new Memcache;
	for($i=0;$i<$hosts_size;$i++) {
		$memcache->pconnect($hosts[$i]['h'],$hosts[$i]['p'] ) or die("add server " .$hosts[$i]['h']. " failed.\n");
		$stats[$id]['host_port'][$hosts[$i]['h'].':'.$hosts[$i]['p']] = $memcache->getStats();
		$memcache->close();
	}

	if(!$opt_migrate) return 0;
	$stats_mig[$id]['time'] = $mictime;
	$memcache = new Memcache;
	for($i=0;$i<$hosts_mig_size;$i++) {
		$memcache->pconnect($hosts_mig[$i]['h'],$hosts_mig[$i]['p'] ) or die("add server " .$hosts_mig[$i]['h']. " failed.\n");
		$stats_mig[$id]['host_port'][$hosts_mig[$i]['h'].':'.$hosts_mig[$i]['p']] = $memcache->getStats();
		$memcache->close();
	}

	return ;
}


function print_percent($hp, $dividend, $divider)
{
	global $hosts;
	global $hosts_size;
	global $stats;
	
	global $hosts_mig;
	global $hosts_mig_size;
	global $stats_mig;
		
	global $items;
	global $ids;
	global $host_ports;	
	global $debug;

	printf("\t[%21s] => ", "$dividend/$divider");
	foreach($ids as $id) {
		if($stats[$id]['host_port'][$hp][$divider] > 0) {
			printf("%12d%%", round($stats[$id]['host_port'][$hp][$dividend]/$stats[$id]['host_port'][$hp][$divider]*100));
		} else {
			printf("%12s%%", $stats[$id]['host_port'][$hp][$dividend] .'/0');
		}
	}
	printf("\n");	
}

function print_percent_mig($hp, $dividend, $divider)
{
	global $hosts_mig;
	global $hosts_mig_size;
	global $stats_mig;
		
	global $items;
	global $ids;
	global $host_ports;	
	global $debug;

	printf("\t[%21s] => ", "$dividend/$divider");
	foreach($ids as $id) {
		if($stats_mig[$id]['host_port'][$hp][$divider] > 0) {
			printf("%12d%%", round($stats_mig[$id]['host_port'][$hp][$dividend]/$stats_mig[$id]['host_port'][$hp][$divider]*100));
		} else {
			printf("%12s%%", $stats_mig[$id]['host_port'][$hp][$dividend] .'/0');
		}
	}
	printf("\n");	
}

$M = 1024*1024;
$K = 1024;
$G = $M*$K;

function div_kd($v, $w = 4, $l0 = 0)
{
	global $M,$K,$G;

	if($v > $G) 
		return sprintf("%d",round($v / $G,$l0)) . 'G';
	if($v > $M) 
		return sprintf("%d",round($v / $M,$l0)) . 'M';
	if($v > $K) 
		return sprintf("%d",round($v / $K,$l0)) . 'K';
	return sprintf("%d",round($v,$l0));
}


function div_k($v, $w = 4, $l0 = 0)
{
	global $M,$K,$G;

	if($v > $G) 
#		return sprintf(" %$wei.01f",round($v / $G,$l0)) . 'G';
		return sprintf(" %3d",round($v / $G,$l0)) . 'G';
	if($v > $M) 
		return sprintf(" %3d",round($v / $M,$l0)) . 'M';
	if($v > $K) 
		return sprintf(" %3d",round($v / $K,$l0)) . 'K';
	return sprintf(" %4d",round($v,$l0));
}

function sar_head()
{


	$sar_header = sprintf(" %5s", 'c_c');
	$sar_header .= sprintf(" %5s", 'con');
	$sar_header .= sprintf(" %5s", 'get');
	$sar_header .= sprintf(" %5s", 'set');
	$sar_header .= sprintf(" %5s", 'hit%');
	$sar_header .= sprintf(" %5s", 'mis');
	$sar_header .= sprintf(" %5s", 'read');
	$sar_header .= sprintf(" %5s", 'writ');
	$sar_header .= sprintf(" %5s", 'maxb');
	$sar_header .= sprintf(" %5s", 'used');
	$sar_header .= sprintf(" %5s", 'c_it');
	$sar_header .= sprintf(" %5s", 't_i');
	$sar_header .= sprintf(" %5s", 'evic');
	$sar_header .= sprintf(" %8s", 't/u/s');
	printf("%s:SAR:%10s:%s\n", '                   ', "HEAD", $sar_header);

	$sar_header = sprintf(" %5s", '---');
	$sar_header .= sprintf(" %5s", '---');
	$sar_header .= sprintf(" %5s", '---');
	$sar_header .= sprintf(" %5s", '---');
	$sar_header .= sprintf(" %5s", '----');
	$sar_header .= sprintf(" %5s", '---');
	$sar_header .= sprintf(" %5s", '----');
	$sar_header .= sprintf(" %5s", '----');
	$sar_header .= sprintf(" %5s", '----');
	$sar_header .= sprintf(" %5s", '----');
	$sar_header .= sprintf(" %5s", '----');
	$sar_header .= sprintf(" %5s", '---');
	$sar_header .= sprintf(" %5s", '----');
	$sar_header .= sprintf(" %8s", '------');
	printf("%s:SAR:%10s:%s\n", '                   ', "HEAD", $sar_header);

}

#$items_sar = array('curr_connections', 'total_connections', 'cmd_get', 'cmd_set', 'get_hits', 'get_misses', 'bytes_read', 'bytes_written', 'limit_maxbytes', 'bytes', 'curr_items', 'total_items', 'evictions');

function sar($t, $cur, $pre)
{
	global $hosts;
	global $hosts_size;
	global $stats;
	global $items;
	global $ids;
	global $host_ports;	
	global $debug;
	global $opt_stats;

	$delta_total = array();
	@$delta_time = $stats[$cur]['time'] - $stats[$pre]['time'];

	$items_sar = 0;
	$s1 = '';
	foreach($host_ports as $hp) {
		$delta = array();
		if(!$items_sar) $items_sar = array_keys($stats[$cur]['host_port'][$hp]);
		foreach($items_sar as $item) {
			if(!in_array($item, array('curr_connections','curr_items','limit_maxbytes','bytes','rusage_user','rusage_system','threads'))) 
				@$delta[$item] = ($stats[$cur]['host_port'][$hp][$item] - $stats[$pre]['host_port'][$hp][$item]) / $delta_time;
			else {
				@$delta[$item] = ($stats[$cur]['host_port'][$hp][$item] + $stats[$pre]['host_port'][$hp][$item])/2;
			}
			@$delta_total[$item] += $delta[$item];
		}

		$s1 = '';
		$s1 .= sprintf(" %s", div_k($delta['curr_connections']));
		$s1 .= sprintf(" %s", div_k($delta['total_connections']) ); 
		$s1 .= sprintf(" %s", div_k($delta['cmd_get']) );
		$s1 .= sprintf(" %s", div_k($delta['cmd_set']) ); 
	if($delta['cmd_get']>0)
		$s1 .= sprintf(" %4d%%", round($delta['get_hits'] / $delta['cmd_get']*100, 0));
	else
		$s1 .= sprintf(" %4d/0%%", round($delta['get_hits'], 0));
		$s1 .= sprintf(" %s", div_k($delta['get_misses']) ); 
		$s1 .= sprintf(" %s", div_k($delta['bytes_read']) ); 
		$s1 .= sprintf(" %s", div_k($delta['bytes_written']) );
		$s1 .= sprintf(" %s", div_k($delta['limit_maxbytes']));
#		$s1 .= sprintf(" %s", div_k($delta['bytes']) );
		$s1 .= sprintf(" %4d%%", round($delta['bytes'] / $delta['limit_maxbytes']*100, 0));
		$s1 .= sprintf(" %s", div_k($delta['curr_items']));
		$s1 .= sprintf(" %s", div_k($delta['total_items']) );
		$s1 .= sprintf(" %s", div_k($delta['evictions']) );		
		$s1 .= sprintf(" %s/%s/%s", div_kd(round($delta['threads'])),div_kd(round($delta['rusage_user'])),div_kd(round($delta['rusage_system'])) );
		printf("%s:SAR:%10s:%s\n", $t, substr($hp,0,6).'~'.substr($hp,-3,3), $s1);
	}

if($hosts_size>1) {
	$s1 = '';
	$s1 .= sprintf(" %s", div_k($delta_total['curr_connections']));
	$s1 .= sprintf(" %s", div_k($delta_total['total_connections']) ); 
	$s1 .= sprintf(" %s", div_k($delta_total['cmd_get']) );
	$s1 .= sprintf(" %s", div_k($delta_total['cmd_set']) ); 
  	if($delta_total['cmd_get'] > 0)
	$s1 .= sprintf(" %4d%%", round($delta_total['get_hits'] / $delta_total['cmd_get']*100, 0));
	else
	$s1 .= sprintf(" %4d/0%%", round($delta_total['get_hits'], 0));
	$s1 .= sprintf(" %s", div_k($delta_total['get_misses']) ); 
	$s1 .= sprintf(" %s", div_k($delta_total['bytes_read']) ); 
	$s1 .= sprintf(" %s", div_k($delta_total['bytes_written']) );
	$s1 .= sprintf(" %s", div_k($delta_total['limit_maxbytes']));
	$s1 .= sprintf(" %4d%%", round($delta_total['bytes'] / $delta_total['limit_maxbytes']*100, 0));
#	$s1 .= sprintf(" %s", div_k($delta_total['bytes']) );
	$s1 .= sprintf(" %s", div_k($delta_total['curr_items']));
	$s1 .= sprintf(" %s", div_k($delta_total['total_items']) );
	$s1 .= sprintf(" %s", div_k($delta_total['evictions']) );
	$s1 .= sprintf("  %s/%s/%s", div_kd(round($delta_total['threads']/$hosts_size)),div_kd(round($delta_total['rusage_user']/$hosts_size)),div_kd(round($delta_total['rusage_system']/$hosts_size)) );

	printf("%s:SAR:%10s:%s\n", $t, "TOTAL", $s1);
}

}

function print_stat()
{
	global $op_stat;	#op_stat[$k1][$k2]
	
	$k1 = 0;$k2 = 0;
	printf("operation stat: ");
	foreach(array_keys($op_stat) as $k1) {
		foreach(array_keys($op_stat[$k1]) as $k2) {
			printf("\t%s-%s:%s", $k1,$k2, $op_stat[$k1][$k2]);
		}
	}
	print "\n";	
}



function get_Stats_stat($is_print = 0)
{
	global $hosts;
	global $hosts_size;
	global $stats;
	
	global $hosts_mig;
	global $hosts_mig_size;
	global $stats_mig;
    global $opt_migrate;
		
	global $items;
	global $ids;
	global $host_ports;

	$items = array();
	$ids = array_keys($stats);
	$host_ports = array_keys($stats[$ids[0]]['host_port']);
	$items = array_keys($stats[$ids[0]]['host_port'][$host_ports[0]]);
	
	if(! $is_print) return ;
	foreach($host_ports as $hp) {
		printf("hostname: $hp\nArray\n(\n\t%21s", "item");
		foreach($ids as $id) {	printf("\t%12s", $id); }  
		printf("%12s","delta"); printf("%12s","ops");
		printf("\n\t "); for($i=0;$i<21;$i++) print '-'; print '    '; for($i=0;$i<12;$i++) print '-';print ' '; for($i=0;$i<12;$i++) print '-';print ' '; for($i=0;$i<12;$i++) print '-';        
		printf("\n");
		foreach($items as $item) {
			printf("\t[%21s] => ", $item);
			$lastid = '';
			foreach($ids as $id) {
				if(!$lastid) $lastid = $id;
				printf("%12d", $stats[$id]['host_port'][$hp][$item]);
				if($lastid and $id != $lastid) {
					$delta = $stats[$id]['host_port'][$hp][$item] - $stats[$lastid]['host_port'][$hp][$item];
					printf(" = %10d",$delta); 
					$delta_time = $stats[$id]['host_port'][$hp]['time'] - $stats[$lastid]['host_port'][$hp]['time'];
					if($delta_time == 0) 
						printf("%s/0", $delta);
					else
						printf(" = %10d",round($delta/$delta_time));
				}
			}
			printf("\n");
		}
		printf("\t--------------------- performance -----------------------------\n");
		print_percent($hp, 'get_hits', 'cmd_get');
		print_percent($hp, 'cmd_set', 'cmd_get');
		print_percent($hp, 'get_misses', 'cmd_get');
		print_percent($hp, 'evictions', 'total_items');
		print_percent($hp, 'bytes', 'limit_maxbytes');

		printf("\n)\n");
	}
	
	if(!$opt_migrate) return 0;
	$items = array();
	$ids = array_keys($stats_mig);
	$host_ports = array_keys($stats_mig[$ids[0]]['host_port']);
	$items = array_keys($stats_mig[$ids[0]]['host_port'][$host_ports[0]]);
	
	if(! $is_print) return ;
	foreach($host_ports as $hp) {
		printf("migrate hostname: $hp\nArray\n(\n\t%21s", "item");
		foreach($ids as $id) {	printf("\t%12s", $id); }  
		printf("%12s","delta"); printf("%12s","ops");
		printf("\n\t "); for($i=0;$i<21;$i++) print '-'; print '    '; for($i=0;$i<12;$i++) print '-';print ' '; for($i=0;$i<12;$i++) print '-';print ' '; for($i=0;$i<12;$i++) print '-';        
		printf("\n");
		foreach($items as $item) {
			printf("\t[%21s] => ", $item);
			$lastid = '';
			foreach($ids as $id) {
				if(!$lastid) $lastid = $id;
				printf("%12d", $stats_mig[$id]['host_port'][$hp][$item]);
				if($lastid and $id != $lastid) {
					$delta = $stats_mig[$id]['host_port'][$hp][$item] - $stats_mig[$lastid]['host_port'][$hp][$item];
					printf(" = %10d",$delta); 
					$delta_time = $stats_mig[$id]['host_port'][$hp]['time'] - $stats_mig[$lastid]['host_port'][$hp]['time'];
					if($delta_time == 0) 
						printf("%s/0", $delta);
					else
						printf(" = %10d",round($delta/$delta_time));
				}
			}
			printf("\n");
		}
		printf("\t--------------------- performance -----------------------------\n");
		print_percent_mig($hp, 'get_hits', 'cmd_get');
		print_percent_mig($hp, 'cmd_set', 'cmd_get');
		print_percent_mig($hp, 'get_misses', 'cmd_get');
		print_percent_mig($hp, 'evictions', 'total_items');
		print_percent_mig($hp, 'bytes', 'limit_maxbytes');

		printf("\n)\n");
	}	

}

function get_expiretime()
{
	global $opt_memcached_expire;
	return ($opt_memcached_expire <= 2592000) ? time()+$opt_memcached_expire : $opt_memcached_expire;
}

#$opt_migrate
function do_migrate($infile, $outfile, $mode)
{
	global $hosts;
	global $hosts_size;
	global $hosts_mig;
	global $hosts_mig_size;
	global $persistent_tcp;
	global $keys;
	global $opt_outfile;
	global $opt_infile;
	global $opt_memcached_expire;
	global $op_stat;
	global $debug;

	if(!$infile or !file_exists($infile)) die("open file $infile failed.\n");

	$memcache_from = new Memcache;
	if($mode == 'migrate') 
	$memcache_to = new Memcache;

	for($i=0;$i<$hosts_size;$i++) {
		$memcache_from->addServer($hosts[$i]['h'], $hosts[$i]['p'], $persistent_tcp, $hosts[$i]['w']);
	}
	if($mode == 'migrate') 
	for($i=0;$i<$hosts_mig_size;$i++) {
		$memcache_to->addServer($hosts_mig[$i]['h'], $hosts_mig[$i]['p'], $persistent_tcp, $hosts_mig[$i]['w']);
	}
		
	$hdr = 0;$hdw = 0;
	if($infile && $opt_infile) $hdr = fopen($infile, "r") or die("open file $infile to read failed.\n");
	if($outfile && $opt_outfile) $hdw = fopen($outfile, "w") or die("open file $outfile to write failed.\n");

	while(true) {
		$s = ''; $k = ''; $v = '';
		@list($len, $exp, $k, $v) = split(" ", chop(fgets($hdr, 10240)));
		if(!$k) break;
		$get_result = $memcache_from->get($k);
		if($get_result !== FALSE) {
			$op_stat['get']['succ']++;
			if($mode == 'migrate') {
				if($memcache_to->get($k) === FALSE) {
					#need to migrate this item
					if($memcache_to->set($k, $get_result, false, $opt_memcached_expire) === FALSE) {
						if($debug >=1) printf("ERROR:Failed to save data at the server for key %s\n", $k);
						$op_stat['set']['fail']++;
					} else {
						$op_stat['set']['succ']++;
					}
				}
			}
			if($hdw) fwrite($hdw,strlen($get_result) .' '. '0' .' '. $k ."\n") or die("write file $outfile failed.\n");
		} else {
			$op_stat['get']['fail']++; 
			if($debug >=1) printf("WARN:Failed to get data from src server for key %s\n", $k);			
		}
	}

	if($infile && $opt_infile) fclose($hdr);
	if($outfile && $opt_outfile) fclose($hdw);

}

function do_get($key)
{
	global $hosts;
	global $hosts_size;
	global $persistent_tcp;

	$memcache = new Memcache;	
	for($i=0;$i<$hosts_size;$i++) {
		$memcache->addServer($hosts[$i]['h'], $hosts[$i]['p'], $persistent_tcp, $hosts[$i]['w']);
	}
	return $memcache->get($key);
}

function do_set($key, $value, $expire )
{
	global $hosts;
	global $hosts_size;
	global $persistent_tcp;
	global $opt_memcached_expire;

	if(!$expire) $expire = $opt_memcached_expire;
	$memcache = new Memcache;	
	for($i=0;$i<$hosts_size;$i++) {
		$memcache->addServer($hosts[$i]['h'], $hosts[$i]['p'], $persistent_tcp, $hosts[$i]['w']);
	}
	return $memcache->set($key, $value,0 ,$expire);
}

function do_stress($loop, $infile, $outfile)
{
	global $hosts;
	global $hosts_size;
	global $persistent_tcp;
	global $opt_rate;
	global $opt_mysql_latency;
	global $keys;
	global $opt_outfile;
	global $opt_infile;
	global $opt_memcached_expire;
	global $opt_itemsize_adaptor;
	global $op_stat;	
	global $debug;
	
	$memcache = new Memcache;
	
	$hdr = 0;$hdw = 0;
	if($infile && $opt_infile) $hdr = fopen($infile, "r") or die("open file $infile to read failed.\n");
	if($outfile && $opt_outfile) $hdw = fopen($outfile, "w") or die("open file $outfile to write failed.\n");
	
	for($c=0;$c<$loop;$c++) {
		for($i=0;$i<$hosts_size;$i++) {
			$memcache->addServer($hosts[$i]['h'], $hosts[$i]['p'], $persistent_tcp, $hosts[$i]['w']);
		}

		$s = ''; $k = ''; $v = '';
		if($infile && $opt_infile) {
			@list($len, $exp, $k, $v) = split(" ", chop(fgets($hdr, 10240)));
		} else {
			$k = md5(rand());
			$v = $k .'-'. gettimeofday(true);
			#$opt_itemsize_adaptor = $opt['a'] ? $opt['a'] : 1; #item(key,value) size scope. size=100 80-120=100-(1-0.8)*100 - 100+(1-0.8)*100
			if($outfile && $opt_outfile) fputs($hdw, strlen($v) .' '. get_expiretime() .' '. $k .' '. $v ."\n");
		}

		if( $opt_rate == 0 or !($opt_rate == -1) or ($opt_rate != -1 and rand(1,100)/100 < 1-1/($opt_rate+1)) ) {
			#eg: $opt_rate = 1, read 1/write 1,    < 1-1/2=1/2
			#	             10,     1-1/11 ,      < 10/11
			#do_read();
			$get_result = $memcache->get($k);
			if($get_result === FALSE) {
				$op_stat['get']['fail']++; 
				if($debug >=2) printf("WARN:failed to get data for key %s\n", $k);
				if($opt_mysql_latency) usleep($opt_mysql_latency);
				if($memcache->set($k, $v, false, $opt_memcached_expire) === FALSE) {
					$op_stat['set']['fail']++; 
					if($debug >=1) printf("ERROR:Failed to save data at the server for key %s\n", $k);
				} else
					$op_stat['set']['succ']++; 
			} else
				$op_stat['get']['succ']++; 
		} else {
			#do_write();
			if($memcache->set($k, $v, false, $opt_memcached_expire) === FALSE) {
				$op_stat['set']['fail']++; 
				if($debug >=1) printf("ERROR:Failed to save data at the server for key %s\n", $k);
			} else 
				$op_stat['set']['succ']++; 
		}

		$memcache->close();
	}
	if($infile && $opt_infile) fclose($hdr);
	if($outfile && $opt_outfile) fclose($hdw);

}

function stats_dump($scope='all', $type='onlykey')
{
	#$opt_stats_dump
	global $hosts;
	global $hosts_size;
	global $opt_stats_dump;
	global $opt_outfile; 
	global $debug;

	$memcache = new Memcache;
	for($i=0;$i<$hosts_size;$i++) {
		$memcache->pconnect($hosts[$i]['h'],$hosts[$i]['p'] ) or die("add server " .$hosts[$i]['h']. " failed.\n");
		printf("--------- memcached server: %s:%s\n", $hosts[$i]['h'],$hosts[$i]['p']);
		$stats = $memcache->getStats();
		printf("stats: "); print_r($stats);
		$stats_item = $memcache->getStats('items');
		printf("stats_item: \n"); 
		if($debug >= 5) print_r($stats_item); 

		$h_s = array();
		$no_print_header = 1; $one_id = -1;
		foreach(array_keys($stats_item[items]) as $item) {
			if($no_print_header) {
				printf("%7s ", 'itemid: ');
				foreach(array_keys($stats_item[items][$item]) as $name) {
					$t = strlen($name) + 5;
					$h_s[$name] = $t;
					$format = "%" . $t . "s";
					printf($format, $name);
				}
				printf("\n");		
				$no_print_header = 0;
				$one_id = $item;
			}
			printf("%7d ", $item);
			foreach(array_keys($stats_item[items][$one_id]) as $name) {
				$format = "%" . $h_s[$name] . "s";
				printf($format, $stats_item[items][$item][$name]);
			}
			printf("\n");
		}


		$stats_sizes = $memcache->getStats('sizes');
		printf("stats_sizes: "); print_r($stats_sizes);
		$stats_slabs = $memcache->getStats('slabs');
		printf("stats_slabs: \n"); 
		if($debug >= 5) print_r($stats_slabs);
		
		$h_s = array();
		$no_print_header = 1; $one_id = -1;
		foreach(array_keys($stats_slabs) as $slebid) {
			if($no_print_header) {
				printf("%7s ", 'slebid');
				foreach(array_keys($stats_slabs[$slebid]) as $name) {
					$t = strlen($name) + 1;
					$h_s[$name] = $t;
					$format = "%" . $t . "s";
					printf($format, $name);
				}
				printf("\n");
				$no_print_header = 0;
				$one_id = $item;
			}

			$type = gettype($stats_slabs[$slebid]);
			if($type == 'array') {
				printf("%7d ", $slebid);
				foreach(array_keys($stats_slabs[$slebid]) as $name) {
					$format = "%" . $h_s[$name] . "s";
					printf($format, $stats_slabs[$slebid][$name]);
				}
				printf("\n");	
			} elseif($type != 'object') {
				printf("%s\t%s\n", $slebid, $stats_slabs[$slebid]);
			}
					
		}
			
		$slebids = array_keys($stats_item[items]);
		printf("stats_slebids: ");  print_r($slebids);

	$si = 0; $sp = 10;
#	while($si <= $opt_stats_dump) {
		foreach ($slebids as $slebid) {
			if(is_numeric($opt_stats_dump) and $opt_stats_dump > 0) $sp = $opt_stats_dump;
			elseif($scope == 'all') $sp = $stats_slabs[$slebid][total_chunks];

			$stats_cachedump = $memcache->getStats('cachedump', $slebid, $sp);

			if($opt_outfile) $hd = fopen($opt_outfile, "w");
			printf("stats_cachedump slebid $slebid to dump $sp: \n"); 
			if($debug >= 5) print_r($stats_cachedump);
			printf("length  expire    key      value\n");
			foreach(array_keys($stats_cachedump) as $key) {
				if($opt_outfile) {
					fwrite($hd, $stats_cachedump[$key][0] .' '. $stats_cachedump[$key][1] .' '. $key . "\n");
				} else {
					printf("%s %s %s", $stats_cachedump[$key][0],$stats_cachedump[$key][1],$key);
					if($debug >= 5) {
						$value = $memcache->get($key);
						$type = gettype($value);
						if($type == ' array')
							print_r($value);
						elseif($type != 'object')
							printf(" %s", $value); 
						else 
							printf(" %s", 'object');
					}
					printf("\n");
				}
			}
		}
#		$si++;
#	} #while $si
	}
	$memcache->close();
}


###------------------------------------------------- main
echo "option:  "; print_r($opt);
#echo "opt_stats = $opt_stats\n";

if($opt_set) {
	@list($k, $v, $expire) = split(":", $opt_set);
	$ret = do_set($k, $v, $expire);
	if($ret === FALSE)
		printf("STORED failed.\n");
	else
		printf("STORED\n", $ret); 
	exit(0);
}

if($opt_get) {
	$ret = do_get($opt_get);
	printf("key $opt_get value=:\n");
	if(is_array($ret)) 
		print_r($ret);
	elseif($ret === FALSE)
		printf("get nothing\n");
	else
		printf("%s\n", $ret); 
	exit(0);
}

if(array_key_exists('D', $opt) and $opt_stats_dump == 'all') {
	ini_set("memory_limit", -1);
	stats_dump('all', 'onlykey');
	exit(0);
}

if(array_key_exists('D', $opt) and $opt_stats_dump) {
	ini_set("memory_limit", $opt_stats_dump * 10 * 1024 * 1024);
	stats_dump();
	exit(0);
}

if(array_key_exists('S', $opt) and $opt_stats == 0) {
	get_Stats('S', 1);
	get_Stats_stat(1);
	exit(0);
}

if(array_key_exists('S', $opt) and $opt_stats == 'P') {
	get_Stats('S', 1);
	get_Stats_stat(1);
	exit(0);
}

if($opt_stats and is_numeric($opt_stats) and $opt_stats >= 1) {
	$i=0;
	while($loop and $i<$loop or !$loop) {
		$cur = $i % 2;
		$pre = ($i+1) % 2;
		
		$t = strftime("%Y-%m-%d %H:%M:%S");
		#echo "---------------------------------------------datetime: $t\n";
		if($i % 10 == 0) sar_head();
		$t1 = microtime(TRUE);
		get_Stats($cur, $t1);
		get_Stats_stat();
		sar($t, $cur,$pre);
		$i++;
		sleep($opt_stats);
	}
	exit(0);
}


$bd = date("Y/m/d-H:i:s");
$bt = microtime(TRUE);

get_Stats('begin',$bt);

for($i = 0; $i < $parallel ; $i++) {
    $status = 0;
    $pid = pcntl_fork();
    if($pid == -1) {
        echo "fork failed!";
        exit(1);
    } else if($pid > 0) {
        //pcntl_wait($status);
    } else {
		if($opt_migrate == 'filter') {
			do_migrate($infiles[$i],$outfiles[$i],'');
		}elseif($opt_migrate and strstr($opt_migrate,':')) {
			if(count($infiles) > 0 and count($outfiles) > 0) {
				do_migrate($infiles[$i],$outfiles[$i],'migrate');
			} elseif(count($infiles) > 0)
				do_migrate($infiles[$i],NULL,'migrate');
			else {
				printf("-M parameter, must -i is avail and -o is option\n");
				return;
			}
		}elseif($opt_migrate) {
			printf("-M parameter = %s is wrong.not -h hostname:port:weight or not filter\n", $opt_migrate);
			return;
		} elseif($infilename and $outfilename)  {
			do_stress($loop, $infiles[$i],$outfiles[$i]);
		} elseif($infilename) {
            do_stress($loop, $infiles[$i], NULL);
		} elseif($outfilename) {
			do_stress($loop, NULL, $outfiles[$i]);
		} else {
			do_stress($loop,NULL,NULL);
		}
		echo "process $i: ";
		print_stat();
        return;
    }
    $pids[] = $pid;
}


foreach($pids as $pid) {
    pcntl_waitpid($pid, $status);
}

$et = microtime(TRUE);
$ed = date("Y/m/d-H:i:s");

get_Stats('end',$et );

get_Stats_stat(1);

/*
$delta = $et - $bt;
$loop_times = $loop*$parallel;
$loop_qps = round($loop_times/$delta);

fwrite(STDOUT, "result:\n");
fwrite(STDOUT, "\tpersistent_tcp = $persistent_tcp\n");
fwrite(STDOUT, "\tparallel = $parallel\n");
fwrite(STDOUT, "\tmysql_latency(microsecond) = $opt_mysql_latency\n");
fwrite(STDOUT, "\tread/write rate = $opt_rate\n");
fwrite(STDOUT, "\ttotal loop count = $loop_times\n");
fwrite(STDOUT, "\tloop count = $loop\n");
fwrite(STDOUT, "\nresult:\n\tbegin time = $bd\n\tend time = $ed\n\tdelta time = $delta(s)\n");
fwrite(STDOUT, "\tloop qps = $loop_qps\n");
*/
?>
